<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
        "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <link href="../Styles/sure3.css" rel="stylesheet" type="text/css"/>
    <link href="../Styles/base1.css" rel="stylesheet" type="text/css"/>
</head>

<body>
<div class="markdown-body entry-content" itemprop="text">
    <h1><a href="#函数的扩展"  class="anchor" id="user-content-函数的扩展">

    </a>函数的扩展
    </h1>
    <h2><a href="#函数参数的默认值"  class="anchor" id="user-content-函数参数的默认值">

    </a>函数参数的默认值
    </h2>
    <h3><a href="#基本用法"  class="anchor" id="user-content-基本用法">

    </a>基本用法
    </h3>
    <p>ES6 之前，不能直接为函数的参数指定默认值，只能采用变通的方法。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">log</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span>) {
  y <span class="pl-k">=</span> y <span class="pl-k">||</span> <span class="pl-s"><span
                class="pl-pds">'</span>World<span class="pl-pds">'</span></span>;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x, y);
}

<span class="pl-en">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Hello<span class="pl-pds">'</span></span>) <span
                class="pl-c"><span class="pl-c">//</span> Hello World</span>
<span class="pl-en">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Hello<span class="pl-pds">'</span></span>, <span
                class="pl-s"><span class="pl-pds">'</span>China<span class="pl-pds">'</span></span>) <span class="pl-c"><span
                class="pl-c">//</span> Hello China</span>
<span class="pl-en">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Hello<span class="pl-pds">'</span></span>, <span
                class="pl-s"><span class="pl-pds">'</span><span class="pl-pds">'</span></span>) <span class="pl-c"><span
                class="pl-c">//</span> Hello World</span></pre>
    </div>
    <p>上面代码检查函数<code>log</code>的参数<code>y</code>有没有赋值，如果没有，则指定默认值为<code>World</code>。这种写法的缺点在于，如果参数<code>y</code>赋值了，但是对应的布尔值为<code>false</code>，则该赋值不起作用。就像上面代码的最后一行，参数<code>y</code>等于空字符，结果被改为默认值。
    </p>
    <p>为了避免这个问题，通常需要先判断一下参数<code>y</code>是否被赋值，如果没有，再等于默认值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">if</span> (<span
            class="pl-k">typeof</span> y <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">'</span>undefined<span
            class="pl-pds">'</span></span>) {
  y <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>World<span
                class="pl-pds">'</span></span>;
}</pre>
    </div>
    <p>ES6 允许为函数的参数设置默认值，即直接写在参数定义的后面。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">log</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span> <span
            class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>World<span
            class="pl-pds">'</span></span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x, y);
}

<span class="pl-en">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Hello<span class="pl-pds">'</span></span>) <span
                class="pl-c"><span class="pl-c">//</span> Hello World</span>
<span class="pl-en">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Hello<span class="pl-pds">'</span></span>, <span
                class="pl-s"><span class="pl-pds">'</span>China<span class="pl-pds">'</span></span>) <span class="pl-c"><span
                class="pl-c">//</span> Hello China</span>
<span class="pl-en">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Hello<span class="pl-pds">'</span></span>, <span
                class="pl-s"><span class="pl-pds">'</span><span class="pl-pds">'</span></span>) <span class="pl-c"><span
                class="pl-c">//</span> Hello</span></pre>
    </div>
    <p>可以看到，ES6 的写法比 ES5 简洁许多，而且非常自然。下面是另一个例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">Point</span>(<span
            class="pl-smi">x</span> <span class="pl-k">=</span> <span class="pl-c1">0</span>, <span
            class="pl-smi">y</span> <span class="pl-k">=</span> <span class="pl-c1">0</span>) {
  <span class="pl-c1">this</span>.<span class="pl-c1">x</span> <span class="pl-k">=</span> x;
  <span class="pl-c1">this</span>.<span class="pl-c1">y</span> <span class="pl-k">=</span> y;
}

<span class="pl-k">const</span> <span class="pl-c1">p</span> <span class="pl-k">=</span> <span
                class="pl-k">new</span> <span class="pl-en">Point</span>();
p <span class="pl-c"><span class="pl-c">//</span> { x: 0, y: 0 }</span></pre>
    </div>
    <p>除了简洁，ES6
        的写法还有两个好处：首先，阅读代码的人，可以立刻意识到哪些参数是可以省略的，不用查看函数体或文档；其次，有利于将来的代码优化，即使未来的版本在对外接口中，彻底拿掉这个参数，也不会导致以前的代码无法运行。</p>
    <p>参数变量是默认声明的，所以不能用<code>let</code>或<code>const</code>再次声明。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">foo</span>(<span class="pl-smi">x</span> <span class="pl-k">=</span> <span
            class="pl-c1">5</span>) {
  <span class="pl-k">let</span> x <span class="pl-k">=</span> <span class="pl-c1">1</span>; <span class="pl-c"><span
                class="pl-c">//</span> error</span>
  <span class="pl-k">const</span> <span class="pl-c1">x</span> <span class="pl-k">=</span> <span class="pl-c1">2</span>; <span
                class="pl-c"><span class="pl-c">//</span> error</span>
}</pre>
    </div>
    <p>上面代码中，参数变量<code>x</code>是默认声明的，在函数体中，不能用<code>let</code>或<code>const</code>再次声明，否则会报错。</p>
    <p>使用参数默认值时，函数不能有同名参数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 不报错</span>
<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span>, <span
                class="pl-smi">x</span>, <span class="pl-smi">y</span>) {
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
}

<span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span>, <span
                class="pl-smi">x</span>, <span class="pl-smi">y</span> <span class="pl-k">=</span> <span
                class="pl-c1">1</span>) {
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
}
<span class="pl-c"><span class="pl-c">//</span> SyntaxError: Duplicate parameter name not allowed in this context</span></pre>
    </div>
    <p>另外，一个容易忽略的地方是，参数默认值不是传值的，而是每次都重新计算默认值表达式的值。也就是说，参数默认值是惰性求值的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> x <span class="pl-k">=</span> <span
            class="pl-c1">99</span>;
<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">p</span> <span
                class="pl-k">=</span> <span class="pl-smi">x</span> <span class="pl-k">+</span> <span
                class="pl-c1">1</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(p);
}

<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> 100</span>

x <span class="pl-k">=</span> <span class="pl-c1">100</span>;
<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> 101</span></pre>
    </div>
    <p>上面代码中，参数<code>p</code>的默认值是<code>x + 1</code>。这时，每次调用函数<code>foo</code>，都会重新计算<code>x +
        1</code>，而不是默认<code>p</code>等于 100。</p>
    <h3><a href="#与解构赋值默认值结合使用"  class="anchor" id="user-content-与解构赋值默认值结合使用">

    </a>与解构赋值默认值结合使用
    </h3>
    <p>参数默认值可以与解构赋值的默认值，结合起来使用。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>({x, y <span
            class="pl-k">=</span> <span class="pl-c1">5</span>}) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x, y);
}

<span class="pl-en">foo</span>({}) <span class="pl-c"><span class="pl-c">//</span> undefined 5</span>
<span class="pl-en">foo</span>({x<span class="pl-k">:</span> <span class="pl-c1">1</span>}) <span class="pl-c"><span
                class="pl-c">//</span> 1 5</span>
<span class="pl-en">foo</span>({x<span class="pl-k">:</span> <span class="pl-c1">1</span>, y<span class="pl-k">:</span> <span
                class="pl-c1">2</span>}) <span class="pl-c"><span class="pl-c">//</span> 1 2</span>
<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> TypeError: Cannot read property 'x' of undefined</span></pre>
    </div>
    <p>
        上面代码只使用了对象的解构赋值默认值，没有使用函数参数的默认值。只有当函数<code>foo</code>的参数是一个对象时，变量<code>x</code>和<code>y</code>才会通过解构赋值生成。如果函数<code>foo</code>调用时没提供参数，变量<code>x</code>和<code>y</code>就不会生成，从而报错。通过提供函数参数的默认值，就可以避免这种情况。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>({x, y <span
            class="pl-k">=</span> <span class="pl-c1">5</span>} <span class="pl-k">=</span> {}) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x, y);
}

<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> undefined 5</span></pre>
    </div>
    <p>上面代码指定，如果没有提供参数，函数<code>foo</code>的参数默认为一个空对象。</p>
    <p>下面是另一个解构赋值默认值的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">fetch</span>(<span
            class="pl-smi">url</span>, { body <span class="pl-k">=</span> <span class="pl-s"><span
            class="pl-pds">'</span><span class="pl-pds">'</span></span>, method <span class="pl-k">=</span> <span
            class="pl-s"><span class="pl-pds">'</span>GET<span class="pl-pds">'</span></span>, headers <span
            class="pl-k">=</span> {} }) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(method);
}

<span class="pl-en">fetch</span>(<span class="pl-s"><span class="pl-pds">'</span>http://example.com<span class="pl-pds">'</span></span>, {})
<span class="pl-c"><span class="pl-c">//</span> "GET"</span>

<span class="pl-en">fetch</span>(<span class="pl-s"><span class="pl-pds">'</span>http://example.com<span class="pl-pds">'</span></span>)
<span class="pl-c"><span class="pl-c">//</span> 报错</span></pre>
    </div>
    <p>上面代码中，如果函数<code>fetch</code>的第二个参数是一个对象，就可以为它的三个属性设置默认值。这种写法不能省略第二个参数，如果结合函数参数的默认值，就可以省略第二个参数。这时，就出现了双重默认值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">fetch</span>(<span
            class="pl-smi">url</span>, { body <span class="pl-k">=</span> <span class="pl-s"><span
            class="pl-pds">'</span><span class="pl-pds">'</span></span>, method <span class="pl-k">=</span> <span
            class="pl-s"><span class="pl-pds">'</span>GET<span class="pl-pds">'</span></span>, headers <span
            class="pl-k">=</span> {} } <span class="pl-k">=</span> {}) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(method);
}

<span class="pl-en">fetch</span>(<span class="pl-s"><span class="pl-pds">'</span>http://example.com<span class="pl-pds">'</span></span>)
<span class="pl-c"><span class="pl-c">//</span> "GET"</span></pre>
    </div>
    <p>上面代码中，函数<code>fetch</code>没有第二个参数时，函数参数的默认值就会生效，然后才是解构赋值的默认值生效，变量<code>method</code>才会取到默认值<code>GET</code>。</p>
    <p>作为练习，请问下面两种写法有什么差别？</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 写法一</span>
<span class="pl-k">function</span> <span class="pl-en">m1</span>({x <span class="pl-k">=</span> <span
                class="pl-c1">0</span>, y <span class="pl-k">=</span> <span class="pl-c1">0</span>} <span
                class="pl-k">=</span> {}) {
  <span class="pl-k">return</span> [x, y];
}

<span class="pl-c"><span class="pl-c">//</span> 写法二</span>
<span class="pl-k">function</span> <span class="pl-en">m2</span>({x, y} <span class="pl-k">=</span> { x<span
                class="pl-k">:</span> <span class="pl-c1">0</span>, y<span class="pl-k">:</span> <span
                class="pl-c1">0</span> }) {
  <span class="pl-k">return</span> [x, y];
}</pre>
    </div>
    <p>上面两种写法都对函数的参数设定了默认值，区别是写法一函数参数的默认值是空对象，但是设置了对象解构赋值的默认值；写法二函数参数的默认值是一个有具体属性的对象，但是没有设置对象解构赋值的默认值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 函数没有参数的情况</span>
<span class="pl-en">m1</span>() <span class="pl-c"><span class="pl-c">//</span> [0, 0]</span>
<span class="pl-en">m2</span>() <span class="pl-c"><span class="pl-c">//</span> [0, 0]</span>

<span class="pl-c"><span class="pl-c">//</span> x 和 y 都有值的情况</span>
<span class="pl-en">m1</span>({x<span class="pl-k">:</span> <span class="pl-c1">3</span>, y<span
                class="pl-k">:</span> <span class="pl-c1">8</span>}) <span class="pl-c"><span class="pl-c">//</span> [3, 8]</span>
<span class="pl-en">m2</span>({x<span class="pl-k">:</span> <span class="pl-c1">3</span>, y<span
                class="pl-k">:</span> <span class="pl-c1">8</span>}) <span class="pl-c"><span class="pl-c">//</span> [3, 8]</span>

<span class="pl-c"><span class="pl-c">//</span> x 有值，y 无值的情况</span>
<span class="pl-en">m1</span>({x<span class="pl-k">:</span> <span class="pl-c1">3</span>}) <span class="pl-c"><span
                class="pl-c">//</span> [3, 0]</span>
<span class="pl-en">m2</span>({x<span class="pl-k">:</span> <span class="pl-c1">3</span>}) <span class="pl-c"><span
                class="pl-c">//</span> [3, undefined]</span>

<span class="pl-c"><span class="pl-c">//</span> x 和 y 都无值的情况</span>
<span class="pl-en">m1</span>({}) <span class="pl-c"><span class="pl-c">//</span> [0, 0];</span>
<span class="pl-en">m2</span>({}) <span class="pl-c"><span class="pl-c">//</span> [undefined, undefined]</span>

<span class="pl-en">m1</span>({z<span class="pl-k">:</span> <span class="pl-c1">3</span>}) <span class="pl-c"><span
                class="pl-c">//</span> [0, 0]</span>
<span class="pl-en">m2</span>({z<span class="pl-k">:</span> <span class="pl-c1">3</span>}) <span class="pl-c"><span
                class="pl-c">//</span> [undefined, undefined]</span></pre>
    </div>
    <h3><a href="#参数默认值的位置"  class="anchor" id="user-content-参数默认值的位置">

    </a>参数默认值的位置
    </h3>
    <p>通常情况下，定义了默认值的参数，应该是函数的尾参数。因为这样比较容易看出来，到底省略了哪些参数。如果非尾部的参数设置默认值，实际上这个参数是没法省略的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 例一</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">x</span> <span
                class="pl-k">=</span> <span class="pl-c1">1</span>, <span class="pl-smi">y</span>) {
  <span class="pl-k">return</span> [x, y];
}

<span class="pl-en">f</span>() <span class="pl-c"><span class="pl-c">//</span> [1, undefined]</span>
<span class="pl-en">f</span>(<span class="pl-c1">2</span>) <span class="pl-c"><span class="pl-c">//</span> [2, undefined])</span>
<span class="pl-en">f</span>(, <span class="pl-c1">1</span>) <span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-en">f</span>(<span class="pl-c1">undefined</span>, <span class="pl-c1">1</span>) <span
                class="pl-c"><span class="pl-c">//</span> [1, 1]</span>

<span class="pl-c"><span class="pl-c">//</span> 例二</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">x</span>, <span
                class="pl-smi">y</span> <span class="pl-k">=</span> <span class="pl-c1">5</span>, <span
                class="pl-smi">z</span>) {
  <span class="pl-k">return</span> [x, y, z];
}

<span class="pl-en">f</span>() <span class="pl-c"><span class="pl-c">//</span> [undefined, 5, undefined]</span>
<span class="pl-en">f</span>(<span class="pl-c1">1</span>) <span class="pl-c"><span class="pl-c">//</span> [1, 5, undefined]</span>
<span class="pl-en">f</span>(<span class="pl-c1">1</span>, ,<span class="pl-c1">2</span>) <span class="pl-c"><span
                class="pl-c">//</span> 报错</span>
<span class="pl-en">f</span>(<span class="pl-c1">1</span>, <span class="pl-c1">undefined</span>, <span
                class="pl-c1">2</span>) <span class="pl-c"><span class="pl-c">//</span> [1, 5, 2]</span></pre>
    </div>
    <p>上面代码中，有默认值的参数都不是尾参数。这时，无法只省略该参数，而不省略它后面的参数，除非显式输入<code>undefined</code>。</p>
    <p>如果传入<code>undefined</code>，将触发该参数等于默认值，<code>null</code>则没有这个效果。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">foo</span>(<span class="pl-smi">x</span> <span class="pl-k">=</span> <span
            class="pl-c1">5</span>, <span class="pl-smi">y</span> <span class="pl-k">=</span> <span
            class="pl-c1">6</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x, y);
}

<span class="pl-en">foo</span>(<span class="pl-c1">undefined</span>, <span class="pl-c1">null</span>)
<span class="pl-c"><span class="pl-c">//</span> 5 null</span></pre>
    </div>
    <p>上面代码中，<code>x</code>参数对应<code>undefined</code>，结果触发了默认值，<code>y</code>参数等于<code>null</code>，就没有触发默认值。</p>
    <h3><a href="#函数的-length-属性"  class="anchor" id="user-content-函数的-length-属性">

    </a>函数的 length 属性
    </h3>
    <p>指定了默认值以后，函数的<code>length</code>属性，将返回没有指定默认值的参数个数。也就是说，指定了默认值后，<code>length</code>属性将失真。</p>
    <div class="highlight highlight-source-js"><pre>(<span class="pl-k">function</span> (<span class="pl-smi">a</span>) {}).<span
            class="pl-c1">length</span> <span class="pl-c"><span class="pl-c">//</span> 1</span>
(<span class="pl-k">function</span> (<span class="pl-smi">a</span> <span class="pl-k">=</span> <span
                class="pl-c1">5</span>) {}).<span class="pl-c1">length</span> <span class="pl-c"><span
                class="pl-c">//</span> 0</span>
(<span class="pl-k">function</span> (<span class="pl-smi">a</span>, <span class="pl-smi">b</span>, <span class="pl-smi">c</span> <span
                class="pl-k">=</span> <span class="pl-c1">5</span>) {}).<span class="pl-c1">length</span> <span
                class="pl-c"><span class="pl-c">//</span> 2</span></pre>
    </div>
    <p>上面代码中，<code>length</code>属性的返回值，等于函数的参数个数减去指定了默认值的参数个数。比如，上面最后一个函数，定义了 3 个参数，其中有一个参数<code>c</code>指定了默认值，因此<code>length</code>属性等于<code>3</code>减去<code>1</code>，最后得到<code>2</code>。
    </p>
    <p>这是因为<code>length</code>属性的含义是，该函数预期传入的参数个数。某个参数指定默认值以后，预期传入的参数个数就不包括这个参数了。同理，后文的 rest 参数也不会计入<code>length</code>属性。
    </p>
    <div class="highlight highlight-source-js">
        <pre>(<span class="pl-k">function</span>(<span class="pl-k">...</span><span class="pl-v">args</span>) {}).<span
                class="pl-c1">length</span> <span class="pl-c"><span class="pl-c">//</span> 0</span></pre>
    </div>
    <p>如果设置了默认值的参数不是尾参数，那么<code>length</code>属性也不再计入后面的参数了。</p>
    <div class="highlight highlight-source-js"><pre>(<span class="pl-k">function</span> (<span
            class="pl-smi">a</span> <span class="pl-k">=</span> <span class="pl-c1">0</span>, <span
            class="pl-smi">b</span>, <span class="pl-smi">c</span>) {}).<span class="pl-c1">length</span> <span
            class="pl-c"><span class="pl-c">//</span> 0</span>
(<span class="pl-k">function</span> (<span class="pl-smi">a</span>, <span class="pl-smi">b</span> <span
                class="pl-k">=</span> <span class="pl-c1">1</span>, <span class="pl-smi">c</span>) {}).<span
                class="pl-c1">length</span> <span class="pl-c"><span class="pl-c">//</span> 1</span></pre>
    </div>
    <h3><a href="#作用域"  class="anchor" id="user-content-作用域">

    </a>作用域
    </h3>
    <p>一旦设置了参数的默认值，函数进行声明初始化时，参数会形成一个单独的作用域（context）。等到初始化结束，这个作用域就会消失。这种语法行为，在不设置参数默认值时，是不会出现的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> x <span class="pl-k">=</span> <span
            class="pl-c1">1</span>;

<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">x</span>, <span
                class="pl-smi">y</span> <span class="pl-k">=</span> <span class="pl-smi">x</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(y);
}

<span class="pl-en">f</span>(<span class="pl-c1">2</span>) <span class="pl-c"><span
                class="pl-c">//</span> 2</span></pre>
    </div>
    <p>上面代码中，参数<code>y</code>的默认值等于变量<code>x</code>。调用函数<code>f</code>时，参数形成一个单独的作用域。在这个作用域里面，默认值变量<code>x</code>指向第一个参数<code>x</code>，而不是全局变量<code>x</code>，所以输出是<code>2</code>。
    </p>
    <p>再看下面的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> x <span class="pl-k">=</span> <span
            class="pl-c1">1</span>;

<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">y</span> <span
                class="pl-k">=</span> <span class="pl-smi">x</span>) {
  <span class="pl-k">let</span> x <span class="pl-k">=</span> <span class="pl-c1">2</span>;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(y);
}

<span class="pl-en">f</span>() <span class="pl-c"><span class="pl-c">//</span> 1</span></pre>
    </div>
    <p>上面代码中，函数<code>f</code>调用时，参数<code>y = x</code>形成一个单独的作用域。这个作用域里面，变量<code>x</code>本身没有定义，所以指向外层的全局变量<code>x</code>。函数调用时，函数体内部的局部变量<code>x</code>影响不到默认值变量<code>x</code>。
    </p>
    <p>如果此时，全局变量<code>x</code>不存在，就会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">y</span> <span class="pl-k">=</span> <span
            class="pl-smi">x</span>) {
  <span class="pl-k">let</span> x <span class="pl-k">=</span> <span class="pl-c1">2</span>;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(y);
}

<span class="pl-en">f</span>() <span class="pl-c"><span
                class="pl-c">//</span> ReferenceError: x is not defined</span></pre>
    </div>
    <p>下面这样写，也会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> x <span class="pl-k">=</span> <span
            class="pl-c1">1</span>;

<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span> <span
                class="pl-k">=</span> <span class="pl-smi">x</span>) {
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
}

<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> ReferenceError: x is not defined</span></pre>
    </div>
    <p>上面代码中，参数<code>x = x</code>形成一个单独作用域。实际执行的是<code>let x = x</code>，由于暂时性死区的原因，这行代码会报错”x 未定义“。</p>
    <p>如果参数的默认值是一个函数，该函数的作用域也遵守这个规则。请看下面的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> foo <span class="pl-k">=</span> <span
            class="pl-s"><span class="pl-pds">'</span>outer<span class="pl-pds">'</span></span>;

<span class="pl-k">function</span> <span class="pl-en">bar</span>(<span class="pl-en">func</span> <span
                class="pl-k">=</span> () <span class="pl-k">=&gt;</span> <span class="pl-smi">foo</span>) {
  <span class="pl-k">let</span> foo <span class="pl-k">=</span> <span class="pl-s"><span
                class="pl-pds">'</span>inner<span class="pl-pds">'</span></span>;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-en">func</span>());
}

<span class="pl-en">bar</span>(); <span class="pl-c"><span class="pl-c">//</span> outer</span></pre>
    </div>
    <p>
        上面代码中，函数<code>bar</code>的参数<code>func</code>的默认值是一个匿名函数，返回值为变量<code>foo</code>。函数参数形成的单独作用域里面，并没有定义变量<code>foo</code>，所以<code>foo</code>指向外层的全局变量<code>foo</code>，因此输出<code>outer</code>。
    </p>
    <p>如果写成下面这样，就会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">bar</span>(<span class="pl-en">func</span> <span class="pl-k">=</span> () <span class="pl-k">=&gt;</span> <span
            class="pl-smi">foo</span>) {
  <span class="pl-k">let</span> foo <span class="pl-k">=</span> <span class="pl-s"><span
                class="pl-pds">'</span>inner<span class="pl-pds">'</span></span>;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-en">func</span>());
}

<span class="pl-en">bar</span>() <span class="pl-c"><span
                class="pl-c">//</span> ReferenceError: foo is not defined</span></pre>
    </div>
    <p>上面代码中，匿名函数里面的<code>foo</code>指向函数外层，但是函数外层并没有声明变量<code>foo</code>，所以就报错了。</p>
    <p>下面是一个更复杂的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> x <span class="pl-k">=</span> <span
            class="pl-c1">1</span>;
<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span>, <span
                class="pl-en">y</span> <span class="pl-k">=</span> <span class="pl-k">function</span>() { x <span
                class="pl-k">=</span> <span class="pl-c1">2</span>; }) {
  <span class="pl-k">var</span> x <span class="pl-k">=</span> <span class="pl-c1">3</span>;
  <span class="pl-en">y</span>();
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
}

<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> 3</span>
x <span class="pl-c"><span class="pl-c">//</span> 1</span></pre>
    </div>
    <p>上面代码中，函数<code>foo</code>的参数形成一个单独作用域。这个作用域里面，首先声明了变量<code>x</code>，然后声明了变量<code>y</code>，<code>y</code>的默认值是一个匿名函数。这个匿名函数内部的变量<code>x</code>，指向同一个作用域的第一个参数<code>x</code>。函数<code>foo</code>内部又声明了一个内部变量<code>x</code>，该变量与第一个参数<code>x</code>由于不是同一个作用域，所以不是同一个变量，因此执行<code>y</code>后，内部变量<code>x</code>和外部全局变量<code>x</code>的值都没变。
    </p>
    <p>如果将<code>var x = 3</code>的<code>var</code>去除，函数<code>foo</code>的内部变量<code>x</code>就指向第一个参数<code>x</code>，与匿名函数内部的<code>x</code>是一致的，所以最后输出的就是<code>2</code>，而外层的全局变量<code>x</code>依然不受影响。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> x <span class="pl-k">=</span> <span
            class="pl-c1">1</span>;
<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span>, <span
                class="pl-en">y</span> <span class="pl-k">=</span> <span class="pl-k">function</span>() { x <span
                class="pl-k">=</span> <span class="pl-c1">2</span>; }) {
  x <span class="pl-k">=</span> <span class="pl-c1">3</span>;
  <span class="pl-en">y</span>();
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
}

<span class="pl-en">foo</span>() <span class="pl-c"><span class="pl-c">//</span> 2</span>
x <span class="pl-c"><span class="pl-c">//</span> 1</span></pre>
    </div>
    <h3><a href="#应用"  class="anchor" id="user-content-应用">

    </a>应用
    </h3>
    <p>利用参数默认值，可以指定某一个参数不得省略，如果省略就抛出一个错误。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">throwIfMissing</span>() {
  <span class="pl-k">throw</span> <span class="pl-k">new</span> <span class="pl-en">Error</span>(<span
                class="pl-s"><span class="pl-pds">'</span>Missing parameter<span class="pl-pds">'</span></span>);
}

<span class="pl-k">function</span> <span class="pl-en">foo</span>(<span class="pl-smi">mustBeProvided</span> <span
                class="pl-k">=</span> <span class="pl-en">throwIfMissing</span>()) {
  <span class="pl-k">return</span> mustBeProvided;
}

<span class="pl-en">foo</span>()
<span class="pl-c"><span class="pl-c">//</span> Error: Missing parameter</span></pre>
    </div>
    <p>上面代码的<code>foo</code>函数，如果调用的时候没有参数，就会调用默认值<code>throwIfMissing</code>函数，从而抛出一个错误。</p>
    <p>从上面代码还可以看到，参数<code>mustBeProvided</code>的默认值等于<code>throwIfMissing</code>函数的运行结果（注意函数名<code>throwIfMissing</code>之后有一对圆括号），这表明参数的默认值不是在定义时执行，而是在运行时执行。如果参数已经赋值，默认值中的函数就不会运行。
    </p>
    <p>另外，可以将参数默认值设为<code>undefined</code>，表明这个参数是可以省略的。</p>
    <div class="highlight highlight-source-js">
        <pre><span class="pl-k">function</span> <span class="pl-en">foo</span>(<span
                class="pl-smi">optional</span> <span class="pl-k">=</span> <span class="pl-c1">undefined</span>) { ··· }</pre>
    </div>
    <h2><a href="#rest-参数"  class="anchor" id="user-content-rest-参数">

    </a>rest 参数
    </h2>
    <p>ES6 引入 rest 参数（形式为<code>...变量名</code>），用于获取函数的多余参数，这样就不需要使用<code>arguments</code>对象了。rest
        参数搭配的变量是一个数组，该变量将多余的参数放入数组中。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">add</span>(<span class="pl-k">...</span><span class="pl-v">values</span>) {
  <span class="pl-k">let</span> sum <span class="pl-k">=</span> <span class="pl-c1">0</span>;

  <span class="pl-k">for</span> (<span class="pl-k">var</span> val <span class="pl-k">of</span> values) {
    sum <span class="pl-k">+=</span> val;
  }

  <span class="pl-k">return</span> sum;
}

<span class="pl-en">add</span>(<span class="pl-c1">2</span>, <span class="pl-c1">5</span>, <span class="pl-c1">3</span>) <span
                class="pl-c"><span class="pl-c">//</span> 10</span></pre>
    </div>
    <p>上面代码的<code>add</code>函数是一个求和函数，利用 rest 参数，可以向该函数传入任意数目的参数。</p>
    <p>下面是一个 rest 参数代替<code>arguments</code>变量的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span
            class="pl-c">//</span> arguments变量的写法</span>
<span class="pl-k">function</span> <span class="pl-en">sortNumbers</span>() {
  <span class="pl-k">return</span> <span class="pl-c1">Array</span>.<span class="pl-c1">prototype</span>.<span
                class="pl-smi">slice</span>.<span class="pl-c1">call</span>(<span class="pl-c1">arguments</span>).<span
                class="pl-c1">sort</span>();
}

<span class="pl-c"><span class="pl-c">//</span> rest参数的写法</span>
<span class="pl-k">const</span> <span class="pl-c1">sortNumbers</span> <span class="pl-k">=</span> (<span class="pl-k">...</span><span
                class="pl-v">numbers</span>) <span class="pl-k">=&gt;</span> <span class="pl-smi">numbers</span>.<span
                class="pl-c1">sort</span>();</pre>
    </div>
    <p>上面代码的两种写法，比较后可以发现，rest 参数的写法更自然也更简洁。</p>
    <p><code>arguments</code>对象不是数组，而是一个类似数组的对象。所以为了使用数组的方法，必须使用<code>Array.prototype.slice.call</code>先将其转为数组。rest
        参数就不存在这个问题，它就是一个真正的数组，数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组<code>push</code>方法的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">push</span>(<span class="pl-smi">array</span>, <span class="pl-k">...</span><span
            class="pl-v">items</span>) {
  <span class="pl-smi">items</span>.<span class="pl-c1">forEach</span>(<span class="pl-k">function</span>(<span
                class="pl-smi">item</span>) {
    <span class="pl-smi">array</span>.<span class="pl-c1">push</span>(item);
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(item);
  });
}

<span class="pl-k">var</span> a <span class="pl-k">=</span> [];
<span class="pl-en">push</span>(a, <span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span
                class="pl-c1">3</span>)</pre>
    </div>
    <p>注意，rest 参数之后不能再有其他参数（即只能是最后一个参数），否则会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">a</span>, <span
                class="pl-k">...</span><span class="pl-v">b</span>, <span class="pl-smi">c</span>) {
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
}</pre>
    </div>
    <p>函数的<code>length</code>属性，不包括 rest 参数。</p>
    <div class="highlight highlight-source-js"><pre>(<span class="pl-k">function</span>(<span class="pl-smi">a</span>) {}).<span
            class="pl-c1">length</span>  <span class="pl-c"><span class="pl-c">//</span> 1</span>
(<span class="pl-k">function</span>(<span class="pl-k">...</span><span class="pl-v">a</span>) {}).<span class="pl-c1">length</span>  <span
                class="pl-c"><span class="pl-c">//</span> 0</span>
(<span class="pl-k">function</span>(<span class="pl-smi">a</span>, <span class="pl-k">...</span><span
                class="pl-v">b</span>) {}).<span class="pl-c1">length</span>  <span class="pl-c"><span
                class="pl-c">//</span> 1</span></pre>
    </div>
    <h2><a href="#严格模式"  class="anchor" id="user-content-严格模式">

    </a>严格模式
    </h2>
    <p>从 ES5 开始，函数内部可以设定为严格模式。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">doSomething</span>(<span class="pl-smi">a</span>, <span class="pl-smi">b</span>) {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-c"><span class="pl-c">//</span> code</span>
}</pre>
    </div>
    <p>ES2016 做了一点修改，规定只要函数参数使用了默认值、解构赋值、或者扩展运算符，那么函数内部就不能显式设定为严格模式，否则会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">function</span> <span class="pl-en">doSomething</span>(<span class="pl-smi">a</span>, <span
                class="pl-smi">b</span> <span class="pl-k">=</span> <span class="pl-smi">a</span>) {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-c"><span class="pl-c">//</span> code</span>
}

<span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">const</span> <span class="pl-c1">doSomething</span> <span class="pl-k">=</span> <span class="pl-k">function</span> ({a, b}) {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-c"><span class="pl-c">//</span> code</span>
};

<span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">const</span> <span class="pl-c1">doSomething</span> <span class="pl-k">=</span> (<span class="pl-k">...</span><span
                class="pl-v">a</span>) <span class="pl-k">=&gt;</span> {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-c"><span class="pl-c">//</span> code</span>
};

<span class="pl-k">const</span> <span class="pl-c1">obj</span> <span class="pl-k">=</span> {
  <span class="pl-c"><span class="pl-c">//</span> 报错</span>
  <span class="pl-en">doSomething</span>({a, b}) {
    <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
    <span class="pl-c"><span class="pl-c">//</span> code</span>
  }
};</pre>
    </div>
    <p>
        这样规定的原因是，函数内部的严格模式，同时适用于函数体和函数参数。但是，函数执行的时候，先执行函数参数，然后再执行函数体。这样就有一个不合理的地方，只有从函数体之中，才能知道参数是否应该以严格模式执行，但是参数却应该先于函数体执行。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">function</span> <span class="pl-en">doSomething</span>(<span class="pl-smi">value</span> <span
                class="pl-k">=</span> <span class="pl-c1">070</span>) {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-k">return</span> value;
}</pre>
    </div>
    <p>上面代码中，参数<code>value</code>的默认值是八进制数<code>070</code>，但是严格模式下不能用前缀<code>0</code>表示八进制，所以应该报错。但是实际上，JavaScript
        引擎会先成功执行<code>value = 070</code>，然后进入函数体内部，发现需要用严格模式执行，这时才会报错。</p>
    <p>虽然可以先解析函数体代码，再执行参数代码，但是这样无疑就增加了复杂性。因此，标准索性禁止了这种用法，只要参数使用了默认值、解构赋值、或者扩展运算符，就不能显式指定严格模式。</p>
    <p>两种方法可以规避这种限制。第一种是设定全局性的严格模式，这是合法的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-s"><span class="pl-pds">'</span>use strict<span
            class="pl-pds">'</span></span>;

<span class="pl-k">function</span> <span class="pl-en">doSomething</span>(<span class="pl-smi">a</span>, <span
                class="pl-smi">b</span> <span class="pl-k">=</span> <span class="pl-smi">a</span>) {
  <span class="pl-c"><span class="pl-c">//</span> code</span>
}</pre>
    </div>
    <p>第二种是把函数包在一个无参数的立即执行函数里面。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span
            class="pl-c1">doSomething</span> <span class="pl-k">=</span> (<span class="pl-k">function</span> () {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-k">return</span> <span class="pl-k">function</span>(<span class="pl-smi">value</span> <span
                class="pl-k">=</span> <span class="pl-c1">42</span>) {
    <span class="pl-k">return</span> value;
  };
}());</pre>
    </div>
    <h2><a href="#name-属性"  class="anchor" id="user-content-name-属性">

    </a>name 属性
    </h2>
    <p>函数的<code>name</code>属性，返回该函数的函数名。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>() {}
<span class="pl-smi">foo</span>.<span class="pl-c1">name</span> <span class="pl-c"><span
                class="pl-c">//</span> "foo"</span></pre>
    </div>
    <p>这个属性早就被浏览器广泛支持，但是直到 ES6，才将其写入了标准。</p>
    <p>需要注意的是，ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量，ES5 的<code>name</code>属性，会返回空字符串，而 ES6 的<code>name</code>属性会返回实际的函数名。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> <span class="pl-en">f</span> <span
            class="pl-k">=</span> <span class="pl-k">function</span> () {};

<span class="pl-c"><span class="pl-c">//</span> ES5</span>
<span class="pl-smi">f</span>.<span class="pl-c1">name</span> <span class="pl-c"><span class="pl-c">//</span> ""</span>

<span class="pl-c"><span class="pl-c">//</span> ES6</span>
<span class="pl-smi">f</span>.<span class="pl-c1">name</span> <span class="pl-c"><span class="pl-c">//</span> "f"</span></pre>
    </div>
    <p>上面代码中，变量<code>f</code>等于一个匿名函数，ES5 和 ES6 的<code>name</code>属性返回的值不一样。</p>
    <p>如果将一个具名函数赋值给一个变量，则 ES5 和 ES6 的<code>name</code>属性都返回这个具名函数原本的名字。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">bar</span> <span
            class="pl-k">=</span> <span class="pl-k">function</span> <span class="pl-en">baz</span>() {};

<span class="pl-c"><span class="pl-c">//</span> ES5</span>
<span class="pl-smi">bar</span>.<span class="pl-c1">name</span> <span class="pl-c"><span
                class="pl-c">//</span> "baz"</span>

<span class="pl-c"><span class="pl-c">//</span> ES6</span>
<span class="pl-smi">bar</span>.<span class="pl-c1">name</span> <span class="pl-c"><span
                class="pl-c">//</span> "baz"</span></pre>
    </div>
    <p><code>Function</code>构造函数返回的函数实例，<code>name</code>属性的值为<code>anonymous</code>。</p>
    <div class="highlight highlight-source-js">
        <pre>(<span class="pl-k">new</span> <span class="pl-en">Function</span>).<span class="pl-c1">name</span> <span
                class="pl-c"><span class="pl-c">//</span> "anonymous"</span></pre>
    </div>
    <p><code>bind</code>返回的函数，<code>name</code>属性值会加上<code>bound</code>前缀。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>() {};
<span class="pl-smi">foo</span>.<span class="pl-en">bind</span>({}).<span class="pl-c1">name</span> <span
                class="pl-c"><span class="pl-c">//</span> "bound foo"</span>

(<span class="pl-k">function</span>(){}).<span class="pl-en">bind</span>({}).<span class="pl-c1">name</span> <span
                class="pl-c"><span class="pl-c">//</span> "bound "</span></pre>
    </div>
    <h2><a href="#箭头函数"  class="anchor" id="user-content-箭头函数">

    </a>箭头函数
    </h2>
    <h3><a href="#基本用法-1"  class="anchor" id="user-content-基本用法-1">

    </a>基本用法
    </h3>
    <p>ES6 允许使用“箭头”（<code>=&gt;</code>）定义函数。</p>
    <div class="highlight highlight-source-js">
        <pre><span class="pl-k">var</span> <span class="pl-en">f</span> <span class="pl-k">=</span> <span
                class="pl-smi">v</span> <span class="pl-k">=&gt;</span> v;</pre>
    </div>
    <p>上面的箭头函数等同于：</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> <span class="pl-en">f</span> <span
            class="pl-k">=</span> <span class="pl-k">function</span>(<span class="pl-smi">v</span>) {
  <span class="pl-k">return</span> v;
};</pre>
    </div>
    <p>如果箭头函数不需要参数或需要多个参数，就使用一个圆括号代表参数部分。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> <span class="pl-en">f</span> <span
            class="pl-k">=</span> () <span class="pl-k">=&gt;</span> <span class="pl-c1">5</span>;
<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-k">var</span> <span class="pl-en">f</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span> () { <span class="pl-k">return</span> <span class="pl-c1">5</span> };

<span class="pl-k">var</span> <span class="pl-en">sum</span> <span class="pl-k">=</span> (<span
                class="pl-smi">num1</span>, <span class="pl-smi">num2</span>) <span class="pl-k">=&gt;</span> num1 <span
                class="pl-k">+</span> num2;
<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-k">var</span> <span class="pl-en">sum</span> <span class="pl-k">=</span> <span
                class="pl-k">function</span>(<span class="pl-smi">num1</span>, <span class="pl-smi">num2</span>) {
  <span class="pl-k">return</span> num1 <span class="pl-k">+</span> num2;
};</pre>
    </div>
    <p>如果箭头函数的代码块部分多于一条语句，就要使用大括号将它们括起来，并且使用<code>return</code>语句返回。</p>
    <div class="highlight highlight-source-js">
        <pre><span class="pl-k">var</span> <span class="pl-en">sum</span> <span class="pl-k">=</span> (<span
                class="pl-smi">num1</span>, <span class="pl-smi">num2</span>) <span class="pl-k">=&gt;</span> { <span
                class="pl-k">return</span> num1 <span class="pl-k">+</span> num2; }</pre>
    </div>
    <p>由于大括号被解释为代码块，所以如果箭头函数直接返回一个对象，必须在对象外面加上括号，否则会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">let</span> <span class="pl-en">getTempItem</span> <span class="pl-k">=</span> <span
                class="pl-smi">id</span> <span class="pl-k">=&gt;</span> { id<span class="pl-k">:</span> id, name<span
                class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>Temp<span class="pl-pds">"</span></span> };

<span class="pl-c"><span class="pl-c">//</span> 不报错</span>
<span class="pl-k">let</span> <span class="pl-en">getTempItem</span> <span class="pl-k">=</span> <span
                class="pl-smi">id</span> <span class="pl-k">=&gt;</span> ({ id<span class="pl-k">:</span> id, name<span
                class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>Temp<span class="pl-pds">"</span></span> });</pre>
    </div>
    <p>如果箭头函数只有一行语句，且不需要返回值，可以采用下面的写法，就不用写大括号了。</p>
    <div class="highlight highlight-source-js">
        <pre><span class="pl-k">let</span> <span class="pl-en">fn</span> <span class="pl-k">=</span> () <span
                class="pl-k">=&gt;</span> <span class="pl-k">void</span> <span
                class="pl-en">doesNotReturn</span>();</pre>
    </div>
    <p>箭头函数可以与变量解构结合使用。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span
            class="pl-c1">full</span> <span class="pl-k">=</span> ({ first, last }) <span class="pl-k">=&gt;</span> first <span
            class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">'</span> <span class="pl-pds">'</span></span> <span
            class="pl-k">+</span> last;

<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-k">function</span> <span class="pl-en">full</span>(<span class="pl-smi">person</span>) {
  <span class="pl-k">return</span> <span class="pl-smi">person</span>.<span class="pl-smi">first</span> <span
                class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">'</span> <span
                class="pl-pds">'</span></span> <span class="pl-k">+</span> <span class="pl-smi">person</span>.<span
                class="pl-smi">last</span>;
}</pre>
    </div>
    <p>箭头函数使得表达更加简洁。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span
            class="pl-c1">isEven</span> <span class="pl-k">=</span> <span class="pl-smi">n</span> <span class="pl-k">=&gt;</span> n <span
            class="pl-k">%</span> <span class="pl-c1">2</span> <span class="pl-k">==</span> <span class="pl-c1">0</span>;
<span class="pl-k">const</span> <span class="pl-c1">square</span> <span class="pl-k">=</span> <span
                class="pl-smi">n</span> <span class="pl-k">=&gt;</span> n <span class="pl-k">*</span> n;</pre>
    </div>
    <p>上面代码只用了两行，就定义了两个简单的工具函数。如果不用箭头函数，可能就要占用多行，而且还不如现在这样写醒目。</p>
    <p>箭头函数的一个用处是简化回调函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 正常函数写法</span>
[<span class="pl-c1">1</span>,<span class="pl-c1">2</span>,<span class="pl-c1">3</span>].<span class="pl-en">map</span>(<span
                class="pl-k">function</span> (<span class="pl-smi">x</span>) {
  <span class="pl-k">return</span> x <span class="pl-k">*</span> x;
});

<span class="pl-c"><span class="pl-c">//</span> 箭头函数写法</span>
[<span class="pl-c1">1</span>,<span class="pl-c1">2</span>,<span class="pl-c1">3</span>].<span class="pl-en">map</span>(<span
                class="pl-smi">x</span> <span class="pl-k">=&gt;</span> x <span class="pl-k">*</span> x);</pre>
    </div>
    <p>另一个例子是</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 正常函数写法</span>
<span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">values</span>.<span class="pl-c1">sort</span>(<span
                class="pl-k">function</span> (<span class="pl-smi">a</span>, <span class="pl-smi">b</span>) {
  <span class="pl-k">return</span> a <span class="pl-k">-</span> b;
});

<span class="pl-c"><span class="pl-c">//</span> 箭头函数写法</span>
<span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">values</span>.<span class="pl-c1">sort</span>((<span
                class="pl-smi">a</span>, <span class="pl-smi">b</span>) <span class="pl-k">=&gt;</span> a <span
                class="pl-k">-</span> b);</pre>
    </div>
    <p>下面是 rest 参数与箭头函数结合的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span
            class="pl-c1">numbers</span> <span class="pl-k">=</span> (<span class="pl-k">...</span><span class="pl-v">nums</span>) <span
            class="pl-k">=&gt;</span> nums;

<span class="pl-en">numbers</span>(<span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span
                class="pl-c1">3</span>, <span class="pl-c1">4</span>, <span class="pl-c1">5</span>)
<span class="pl-c"><span class="pl-c">//</span> [1,2,3,4,5]</span>

<span class="pl-k">const</span> <span class="pl-c1">headAndTail</span> <span class="pl-k">=</span> (<span
                class="pl-smi">head</span>, <span class="pl-k">...</span><span class="pl-v">tail</span>) <span
                class="pl-k">=&gt;</span> [head, tail];

<span class="pl-en">headAndTail</span>(<span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span
                class="pl-c1">3</span>, <span class="pl-c1">4</span>, <span class="pl-c1">5</span>)
<span class="pl-c"><span class="pl-c">//</span> [1,[2,3,4,5]]</span></pre>
    </div>
    <h3><a href="#使用注意点"  class="anchor" id="user-content-使用注意点">

    </a>使用注意点
    </h3>
    <p>箭头函数有几个使用注意点。</p>
    <p>（1）函数体内的<code>this</code>对象，就是定义时所在的对象，而不是使用时所在的对象。</p>
    <p>（2）不可以当作构造函数，也就是说，不可以使用<code>new</code>命令，否则会抛出一个错误。</p>
    <p>（3）不可以使用<code>arguments</code>对象，该对象在函数体内不存在。如果要用，可以用 rest 参数代替。</p>
    <p>（4）不可以使用<code>yield</code>命令，因此箭头函数不能用作 Generator 函数。</p>
    <p>上面四点中，第一点尤其值得注意。<code>this</code>对象的指向是可变的，但是在箭头函数中，它是固定的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>() {
  <span class="pl-c1">setTimeout</span>(() <span class="pl-k">=&gt;</span> {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>id:<span
                class="pl-pds">'</span></span>, <span class="pl-c1">this</span>.<span class="pl-c1">id</span>);
  }, <span class="pl-c1">100</span>);
}

<span class="pl-k">var</span> id <span class="pl-k">=</span> <span class="pl-c1">21</span>;

<span class="pl-smi">foo</span>.<span class="pl-c1">call</span>({ id<span class="pl-k">:</span> <span
                class="pl-c1">42</span> });
<span class="pl-c"><span class="pl-c">//</span> id: 42</span></pre>
    </div>
    <p>上面代码中，<code>setTimeout</code>的参数是一个箭头函数，这个箭头函数的定义生效是在<code>foo</code>函数生成时，而它的真正执行要等到 100 毫秒后。如果是普通函数，执行时<code>this</code>应该指向全局对象<code>window</code>，这时应该输出<code>21</code>。但是，箭头函数导致<code>this</code>总是指向函数定义生效时所在的对象（本例是<code>{id:
        42}</code>），所以输出的是<code>42</code>。</p>
    <p>箭头函数可以让<code>setTimeout</code>里面的<code>this</code>，绑定定义时所在的作用域，而不是指向运行时所在的作用域。下面是另一个例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">Timer</span>() {
  <span class="pl-c1">this</span>.<span class="pl-smi">s1</span> <span class="pl-k">=</span> <span
                class="pl-c1">0</span>;
  <span class="pl-c1">this</span>.<span class="pl-smi">s2</span> <span class="pl-k">=</span> <span
                class="pl-c1">0</span>;
  <span class="pl-c"><span class="pl-c">//</span> 箭头函数</span>
  <span class="pl-c1">setInterval</span>(() <span class="pl-k">=&gt;</span> <span class="pl-c1">this</span>.<span
                class="pl-smi">s1</span><span class="pl-k">++</span>, <span class="pl-c1">1000</span>);
  <span class="pl-c"><span class="pl-c">//</span> 普通函数</span>
  <span class="pl-c1">setInterval</span>(<span class="pl-k">function</span> () {
    <span class="pl-c1">this</span>.<span class="pl-smi">s2</span><span class="pl-k">++</span>;
  }, <span class="pl-c1">1000</span>);
}

<span class="pl-k">var</span> timer <span class="pl-k">=</span> <span class="pl-k">new</span> <span
                class="pl-en">Timer</span>();

<span class="pl-c1">setTimeout</span>(() <span class="pl-k">=&gt;</span> <span class="pl-en">console</span>.<span
                class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>s1: <span
                class="pl-pds">'</span></span>, <span class="pl-smi">timer</span>.<span class="pl-smi">s1</span>), <span
                class="pl-c1">3100</span>);
<span class="pl-c1">setTimeout</span>(() <span class="pl-k">=&gt;</span> <span class="pl-en">console</span>.<span
                class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>s2: <span
                class="pl-pds">'</span></span>, <span class="pl-smi">timer</span>.<span class="pl-smi">s2</span>), <span
                class="pl-c1">3100</span>);
<span class="pl-c"><span class="pl-c">//</span> s1: 3</span>
<span class="pl-c"><span class="pl-c">//</span> s2: 0</span></pre>
    </div>
    <p>
        上面代码中，<code>Timer</code>函数内部设置了两个定时器，分别使用了箭头函数和普通函数。前者的<code>this</code>绑定定义时所在的作用域（即<code>Timer</code>函数），后者的<code>this</code>指向运行时所在的作用域（即全局对象）。所以，3100
        毫秒之后，<code>timer.s1</code>被更新了 3 次，而<code>timer.s2</code>一次都没更新。</p>
    <p>箭头函数可以让<code>this</code>指向固定化，这种特性很有利于封装回调函数。下面是一个例子，DOM 事件的回调函数封装在一个对象里面。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> handler <span class="pl-k">=</span> {
  id<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>123456<span
                class="pl-pds">'</span></span>,

  <span class="pl-en">init</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {
    <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span
                class="pl-pds">'</span>click<span class="pl-pds">'</span></span>,
      <span class="pl-smi">event</span> <span class="pl-k">=&gt;</span> <span class="pl-c1">this</span>.<span
                class="pl-en">doSomething</span>(<span class="pl-c1">event</span>.<span
                class="pl-c1">type</span>), <span class="pl-c1">false</span>);
  },

  <span class="pl-en">doSomething</span><span class="pl-k">:</span> <span class="pl-k">function</span>(<span
                class="pl-smi">type</span>) {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>Handling <span
                class="pl-pds">'</span></span> <span class="pl-k">+</span> type  <span class="pl-k">+</span> <span
                class="pl-s"><span class="pl-pds">'</span> for <span class="pl-pds">'</span></span> <span
                class="pl-k">+</span> <span class="pl-c1">this</span>.<span class="pl-c1">id</span>);
  }
};</pre>
    </div>
    <p>上面代码的<code>init</code>方法中，使用了箭头函数，这导致这个箭头函数里面的<code>this</code>，总是指向<code>handler</code>对象。否则，回调函数运行时，<code>this.doSomething</code>这一行会报错，因为此时<code>this</code>指向<code>document</code>对象。
    </p>
    <p>
        <code>this</code>指向的固定化，并不是因为箭头函数内部有绑定<code>this</code>的机制，实际原因是箭头函数根本没有自己的<code>this</code>，导致内部的<code>this</code>就是外层代码块的<code>this</code>。正是因为它没有<code>this</code>，所以也就不能用作构造函数。
    </p>
    <p>所以，箭头函数转成 ES5 的代码如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> ES6</span>
<span class="pl-k">function</span> <span class="pl-en">foo</span>() {
  <span class="pl-c1">setTimeout</span>(() <span class="pl-k">=&gt;</span> {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>id:<span
                class="pl-pds">'</span></span>, <span class="pl-c1">this</span>.<span class="pl-c1">id</span>);
  }, <span class="pl-c1">100</span>);
}

<span class="pl-c"><span class="pl-c">//</span> ES5</span>
<span class="pl-k">function</span> <span class="pl-en">foo</span>() {
  <span class="pl-k">var</span> _this <span class="pl-k">=</span> <span class="pl-c1">this</span>;

  <span class="pl-c1">setTimeout</span>(<span class="pl-k">function</span> () {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>id:<span
                class="pl-pds">'</span></span>, <span class="pl-smi">_this</span>.<span class="pl-c1">id</span>);
  }, <span class="pl-c1">100</span>);
}</pre>
    </div>
    <p>上面代码中，转换后的 ES5 版本清楚地说明了，箭头函数里面根本没有自己的<code>this</code>，而是引用外层的<code>this</code>。</p>
    <p>请问下面的代码之中有几个<code>this</code>？</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>() {
  <span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> {
    <span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> {
      <span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> {
        <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span
                class="pl-pds">'</span>id:<span class="pl-pds">'</span></span>, <span class="pl-c1">this</span>.<span
                class="pl-c1">id</span>);
      };
    };
  };
}

<span class="pl-k">var</span> f <span class="pl-k">=</span> <span class="pl-smi">foo</span>.<span
                class="pl-c1">call</span>({id<span class="pl-k">:</span> <span class="pl-c1">1</span>});

<span class="pl-k">var</span> t1 <span class="pl-k">=</span> <span class="pl-smi">f</span>.<span
                class="pl-c1">call</span>({id<span class="pl-k">:</span> <span class="pl-c1">2</span>})()(); <span
                class="pl-c"><span class="pl-c">//</span> id: 1</span>
<span class="pl-k">var</span> t2 <span class="pl-k">=</span> <span class="pl-en">f</span>().<span
                class="pl-c1">call</span>({id<span class="pl-k">:</span> <span class="pl-c1">3</span>})(); <span
                class="pl-c"><span class="pl-c">//</span> id: 1</span>
<span class="pl-k">var</span> t3 <span class="pl-k">=</span> <span class="pl-en">f</span>()().<span
                class="pl-c1">call</span>({id<span class="pl-k">:</span> <span class="pl-c1">4</span>}); <span
                class="pl-c"><span class="pl-c">//</span> id: 1</span></pre>
    </div>
    <p>
        上面代码之中，只有一个<code>this</code>，就是函数<code>foo</code>的<code>this</code>，所以<code>t1</code>、<code>t2</code>、<code>t3</code>都输出同样的结果。因为所有的内层函数都是箭头函数，都没有自己的<code>this</code>，它们的<code>this</code>其实都是最外层<code>foo</code>函数的<code>this</code>。
    </p>
    <p>
        除了<code>this</code>，以下三个变量在箭头函数之中也是不存在的，指向外层函数的对应变量：<code>arguments</code>、<code>super</code>、<code>new.target</code>。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">foo</span>() {
  <span class="pl-c1">setTimeout</span>(() <span class="pl-k">=&gt;</span> {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>args:<span
                class="pl-pds">'</span></span>, <span class="pl-c1">arguments</span>);
  }, <span class="pl-c1">100</span>);
}

<span class="pl-en">foo</span>(<span class="pl-c1">2</span>, <span class="pl-c1">4</span>, <span class="pl-c1">6</span>, <span
                class="pl-c1">8</span>)
<span class="pl-c"><span class="pl-c">//</span> args: [2, 4, 6, 8]</span></pre>
    </div>
    <p>上面代码中，箭头函数内部的变量<code>arguments</code>，其实是函数<code>foo</code>的<code>arguments</code>变量。</p>
    <p>
        另外，由于箭头函数没有自己的<code>this</code>，所以当然也就不能用<code>call()</code>、<code>apply()</code>、<code>bind()</code>这些方法去改变<code>this</code>的指向。
    </p>
    <div class="highlight highlight-source-js"><pre>(<span class="pl-k">function</span>() {
  <span class="pl-k">return</span> [
    (() <span class="pl-k">=&gt;</span> <span class="pl-c1">this</span>.<span class="pl-c1">x</span>).<span
                class="pl-en">bind</span>({ x<span class="pl-k">:</span> <span class="pl-s"><span
                class="pl-pds">'</span>inner<span class="pl-pds">'</span></span> })()
  ];
}).<span class="pl-c1">call</span>({ x<span class="pl-k">:</span> <span class="pl-s"><span
                class="pl-pds">'</span>outer<span class="pl-pds">'</span></span> });
<span class="pl-c"><span class="pl-c">//</span> ['outer']</span></pre>
    </div>
    <p>上面代码中，箭头函数没有自己的<code>this</code>，所以<code>bind</code>方法无效，内部的<code>this</code>指向外部的<code>this</code>。</p>
    <p>长期以来，JavaScript 语言的<code>this</code>对象一直是一个令人头痛的问题，在对象方法中使用<code>this</code>，必须非常小心。箭头函数”绑定”<code>this</code>，很大程度上解决了这个困扰。
    </p>
    <h3><a href="#嵌套的箭头函数"  class="anchor" id="user-content-嵌套的箭头函数">

    </a>嵌套的箭头函数
    </h3>
    <p>箭头函数内部，还可以再使用箭头函数。下面是一个 ES5 语法的多重嵌套函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">insert</span>(<span
            class="pl-smi">value</span>) {
  <span class="pl-k">return</span> {<span class="pl-en">into</span><span class="pl-k">:</span> <span class="pl-k">function</span> (<span
                class="pl-smi">array</span>) {
    <span class="pl-k">return</span> {<span class="pl-en">after</span><span class="pl-k">:</span> <span class="pl-k">function</span> (<span
                class="pl-smi">afterValue</span>) {
      <span class="pl-smi">array</span>.<span class="pl-c1">splice</span>(<span class="pl-smi">array</span>.<span
                class="pl-c1">indexOf</span>(afterValue) <span class="pl-k">+</span> <span class="pl-c1">1</span>, <span
                class="pl-c1">0</span>, value);
      <span class="pl-k">return</span> array;
    }};
  }};
}

<span class="pl-en">insert</span>(<span class="pl-c1">2</span>).<span class="pl-en">into</span>([<span
                class="pl-c1">1</span>, <span class="pl-c1">3</span>]).<span class="pl-c1">after</span>(<span
                class="pl-c1">1</span>); <span class="pl-c"><span class="pl-c">//</span>[1, 2, 3]</span></pre>
    </div>
    <p>上面这个函数，可以使用箭头函数改写。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> <span
            class="pl-en">insert</span> <span class="pl-k">=</span> (<span class="pl-smi">value</span>) <span
            class="pl-k">=&gt;</span> ({<span class="pl-en">into</span><span class="pl-k">:</span> (<span
            class="pl-smi">array</span>) <span class="pl-k">=&gt;</span> ({<span class="pl-en">after</span><span
            class="pl-k">:</span> (<span class="pl-smi">afterValue</span>) <span class="pl-k">=&gt;</span> {
  <span class="pl-smi">array</span>.<span class="pl-c1">splice</span>(<span class="pl-smi">array</span>.<span
                class="pl-c1">indexOf</span>(afterValue) <span class="pl-k">+</span> <span class="pl-c1">1</span>, <span
                class="pl-c1">0</span>, value);
  <span class="pl-k">return</span> array;
}})});

<span class="pl-en">insert</span>(<span class="pl-c1">2</span>).<span class="pl-en">into</span>([<span
                class="pl-c1">1</span>, <span class="pl-c1">3</span>]).<span class="pl-c1">after</span>(<span
                class="pl-c1">1</span>); <span class="pl-c"><span class="pl-c">//</span>[1, 2, 3]</span></pre>
    </div>
    <p>下面是一个部署管道机制（pipeline）的例子，即前一个函数的输出是后一个函数的输入。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">pipeline</span> <span
            class="pl-k">=</span> (<span class="pl-k">...</span><span class="pl-v">funcs</span>) <span class="pl-k">=&gt;</span>
  <span class="pl-smi">val</span> <span class="pl-k">=&gt;</span> <span class="pl-smi">funcs</span>.<span class="pl-en">reduce</span>((<span
                class="pl-smi">a</span>, <span class="pl-smi">b</span>) <span class="pl-k">=&gt;</span> <span
                class="pl-en">b</span>(a), val);

<span class="pl-k">const</span> <span class="pl-c1">plus1</span> <span class="pl-k">=</span> <span
                class="pl-smi">a</span> <span class="pl-k">=&gt;</span> a <span class="pl-k">+</span> <span
                class="pl-c1">1</span>;
<span class="pl-k">const</span> <span class="pl-c1">mult2</span> <span class="pl-k">=</span> <span
                class="pl-smi">a</span> <span class="pl-k">=&gt;</span> a <span class="pl-k">*</span> <span
                class="pl-c1">2</span>;
<span class="pl-k">const</span> <span class="pl-c1">addThenMult</span> <span class="pl-k">=</span> <span class="pl-en">pipeline</span>(plus1, mult2);

<span class="pl-en">addThenMult</span>(<span class="pl-c1">5</span>)
<span class="pl-c"><span class="pl-c">//</span> 12</span></pre>
    </div>
    <p>如果觉得上面的写法可读性比较差，也可以采用下面的写法。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span
            class="pl-c1">plus1</span> <span class="pl-k">=</span> <span class="pl-smi">a</span> <span class="pl-k">=&gt;</span> a <span
            class="pl-k">+</span> <span class="pl-c1">1</span>;
<span class="pl-k">const</span> <span class="pl-c1">mult2</span> <span class="pl-k">=</span> <span
                class="pl-smi">a</span> <span class="pl-k">=&gt;</span> a <span class="pl-k">*</span> <span
                class="pl-c1">2</span>;

<span class="pl-en">mult2</span>(<span class="pl-en">plus1</span>(<span class="pl-c1">5</span>))
<span class="pl-c"><span class="pl-c">//</span> 12</span></pre>
    </div>
    <p>箭头函数还有一个功能，就是可以很方便地改写 λ 演算。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> λ演算的写法</span>
fix <span class="pl-k">=</span> λf.(λ<span class="pl-smi">x</span>.<span class="pl-en">f</span>(λ<span
                class="pl-smi">v</span>.<span class="pl-c1">x</span>(x)(v)))(λ<span class="pl-smi">x</span>.<span
                class="pl-en">f</span>(λ<span class="pl-smi">v</span>.<span class="pl-c1">x</span>(x)(v)))

<span class="pl-c"><span class="pl-c">//</span> ES6的写法</span>
<span class="pl-k">var</span> <span class="pl-en">fix</span> <span class="pl-k">=</span> <span
                class="pl-smi">f</span> <span class="pl-k">=&gt;</span> (<span class="pl-smi">x</span> <span
                class="pl-k">=&gt;</span> <span class="pl-en">f</span>(<span class="pl-smi">v</span> <span class="pl-k">=&gt;</span> <span
                class="pl-en">x</span>(x)(v)))
               (<span class="pl-smi">x</span> <span class="pl-k">=&gt;</span> <span class="pl-en">f</span>(<span
                class="pl-smi">v</span> <span class="pl-k">=&gt;</span> <span class="pl-en">x</span>(x)(v)));</pre>
    </div>
    <p>上面两种写法，几乎是一一对应的。由于 λ 演算对于计算机科学非常重要，这使得我们可以用 ES6 作为替代工具，探索计算机科学。</p>
    <h2><a href="#双冒号运算符"  class="anchor" id="user-content-双冒号运算符">

    </a>双冒号运算符
    </h2>
    <p>
        箭头函数可以绑定<code>this</code>对象，大大减少了显式绑定<code>this</code>对象的写法（<code>call</code>、<code>apply</code>、<code>bind</code>）。但是，箭头函数并不适用于所有场合，所以现在有一个<a
            href="https://github.com/zenparsing/es-function-bind">提案</a>，提出了“函数绑定”（function
        bind）运算符，用来取代<code>call</code>、<code>apply</code>、<code>bind</code>调用。</p>
    <p>函数绑定运算符是并排的两个冒号（<code>::</code>），双冒号左边是一个对象，右边是一个函数。该运算符会自动将左边的对象，作为上下文环境（即<code>this</code>对象），绑定到右边的函数上面。</p>
    <div class="highlight highlight-source-js"><pre>foo<span class="pl-k">::</span>bar;
<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-smi">bar</span>.<span class="pl-en">bind</span>(foo);

foo<span class="pl-k">::</span><span class="pl-en">bar</span>(<span class="pl-k">...</span><span
                class="pl-c1">arguments</span>);
<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-smi">bar</span>.<span class="pl-c1">apply</span>(foo, <span class="pl-c1">arguments</span>);

<span class="pl-k">const</span> <span class="pl-c1">hasOwnProperty</span> <span class="pl-k">=</span> <span
                class="pl-c1">Object</span>.<span class="pl-c1">prototype</span>.<span
                class="pl-smi">hasOwnProperty</span>;
<span class="pl-k">function</span> <span class="pl-en">hasOwn</span>(<span class="pl-smi">obj</span>, <span
                class="pl-smi">key</span>) {
  <span class="pl-k">return</span> obj<span class="pl-k">::</span><span class="pl-en">hasOwnProperty</span>(key);
}</pre>
    </div>
    <p>如果双冒号左边为空，右边是一个对象的方法，则等于将该方法绑定在该对象上面。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> method <span class="pl-k">=</span> obj<span
            class="pl-k">::</span><span class="pl-smi">obj</span>.<span class="pl-smi">foo</span>;
<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-k">var</span> method <span class="pl-k">=</span> <span class="pl-k">::</span><span
                class="pl-smi">obj</span>.<span class="pl-smi">foo</span>;

<span class="pl-k">let</span> log <span class="pl-k">=</span> <span class="pl-k">::</span><span
                class="pl-en">console</span>.<span class="pl-smi">log</span>;
<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-k">var</span> log <span class="pl-k">=</span> <span class="pl-en">console</span>.<span class="pl-smi">log</span>.<span
                class="pl-en">bind</span>(<span class="pl-en">console</span>);</pre>
    </div>
    <p>双冒号运算符的运算结果，还是一个对象，因此可以采用链式写法。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 例一</span>
<span class="pl-k">import</span> { <span class="pl-smi">map</span>, <span class="pl-smi">takeWhile</span>, <span
                class="pl-smi">forEach</span> } <span class="pl-k">from</span> <span class="pl-s"><span
                class="pl-pds">"</span>iterlib<span class="pl-pds">"</span></span>;

<span class="pl-en">getPlayers</span>()
<span class="pl-k">::</span><span class="pl-en">map</span>(<span class="pl-smi">x</span> <span class="pl-k">=&gt;</span> <span
                class="pl-smi">x</span>.<span class="pl-en">character</span>())
<span class="pl-k">::</span><span class="pl-en">takeWhile</span>(<span class="pl-smi">x</span> <span
                class="pl-k">=&gt;</span> <span class="pl-smi">x</span>.<span class="pl-smi">strength</span> <span
                class="pl-k">&gt;</span> <span class="pl-c1">100</span>)
<span class="pl-k">::</span><span class="pl-en">forEach</span>(<span class="pl-smi">x</span> <span
                class="pl-k">=&gt;</span> <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x));

<span class="pl-c"><span class="pl-c">//</span> 例二</span>
<span class="pl-k">let</span> { find, html } <span class="pl-k">=</span> jake;

<span class="pl-c1">document</span>.<span class="pl-c1">querySelectorAll</span>(<span class="pl-s"><span class="pl-pds">"</span>div.myClass<span
                class="pl-pds">"</span></span>)
<span class="pl-k">::</span><span class="pl-en">find</span>(<span class="pl-s"><span class="pl-pds">"</span>p<span
                class="pl-pds">"</span></span>)
<span class="pl-k">::</span><span class="pl-en">html</span>(<span class="pl-s"><span class="pl-pds">"</span>hahaha<span
                class="pl-pds">"</span></span>);</pre>
    </div>
    <h2><a href="#尾调用优化"  class="anchor" id="user-content-尾调用优化">

    </a>尾调用优化
    </h2>
    <h3><a href="#什么是尾调用"  class="anchor" id="user-content-什么是尾调用">

    </a>什么是尾调用？
    </h3>
    <p>尾调用（Tail Call）是函数式编程的一个重要概念，本身非常简单，一句话就能说清楚，就是指某个函数的最后一步是调用另一个函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">x</span>){
  <span class="pl-k">return</span> <span class="pl-en">g</span>(x);
}</pre>
    </div>
    <p>上面代码中，函数<code>f</code>的最后一步是调用函数<code>g</code>，这就叫尾调用。</p>
    <p>以下三种情况，都不属于尾调用。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 情况一</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">x</span>){
  <span class="pl-k">let</span> y <span class="pl-k">=</span> <span class="pl-en">g</span>(x);
  <span class="pl-k">return</span> y;
}

<span class="pl-c"><span class="pl-c">//</span> 情况二</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">x</span>){
  <span class="pl-k">return</span> <span class="pl-en">g</span>(x) <span class="pl-k">+</span> <span
                class="pl-c1">1</span>;
}

<span class="pl-c"><span class="pl-c">//</span> 情况三</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>(<span class="pl-smi">x</span>){
  <span class="pl-en">g</span>(x);
}</pre>
    </div>
    <p>上面代码中，情况一是调用函数<code>g</code>之后，还有赋值操作，所以不属于尾调用，即使语义完全一样。情况二也属于调用后还有操作，即使写在一行内。情况三等同于下面的代码。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">x</span>){
  <span class="pl-en">g</span>(x);
  <span class="pl-k">return</span> <span class="pl-c1">undefined</span>;
}</pre>
    </div>
    <p>尾调用不一定出现在函数尾部，只要是最后一步操作即可。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">f</span>(<span class="pl-smi">x</span>) {
  <span class="pl-k">if</span> (x <span class="pl-k">&gt;</span> <span class="pl-c1">0</span>) {
    <span class="pl-k">return</span> <span class="pl-en">m</span>(x)
  }
  <span class="pl-k">return</span> <span class="pl-en">n</span>(x);
}</pre>
    </div>
    <p>上面代码中，函数<code>m</code>和<code>n</code>都属于尾调用，因为它们都是函数<code>f</code>的最后一步操作。</p>
    <h3><a href="#尾调用优化-1"  class="anchor" id="user-content-尾调用优化-1">

    </a>尾调用优化
    </h3>
    <p>尾调用之所以与其他调用不同，就在于它的特殊的调用位置。</p>
    <p>我们知道，函数调用会在内存形成一个“调用记录”，又称“调用帧”（call
        frame），保存调用位置和内部变量等信息。如果在函数<code>A</code>的内部调用函数<code>B</code>，那么在<code>A</code>的调用帧上方，还会形成一个<code>B</code>的调用帧。等到<code>B</code>运行结束，将结果返回到<code>A</code>，<code>B</code>的调用帧才会消失。如果函数<code>B</code>内部还调用函数<code>C</code>，那就还有一个<code>C</code>的调用帧，以此类推。所有的调用帧，就形成一个“调用栈”（call
        stack）。</p>
    <p>尾调用由于是函数的最后一步操作，所以不需要保留外层函数的调用帧，因为调用位置、内部变量等信息都不会再用到了，只要直接用内层函数的调用帧，取代外层函数的调用帧就可以了。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">f</span>() {
  <span class="pl-k">let</span> m <span class="pl-k">=</span> <span class="pl-c1">1</span>;
  <span class="pl-k">let</span> n <span class="pl-k">=</span> <span class="pl-c1">2</span>;
  <span class="pl-k">return</span> <span class="pl-en">g</span>(m <span class="pl-k">+</span> n);
}
<span class="pl-en">f</span>();

<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-k">function</span> <span class="pl-en">f</span>() {
  <span class="pl-k">return</span> <span class="pl-en">g</span>(<span class="pl-c1">3</span>);
}
<span class="pl-en">f</span>();

<span class="pl-c"><span class="pl-c">//</span> 等同于</span>
<span class="pl-en">g</span>(<span class="pl-c1">3</span>);</pre>
    </div>
    <p>上面代码中，如果函数<code>g</code>不是尾调用，函数<code>f</code>就需要保存内部变量<code>m</code>和<code>n</code>的值、<code>g</code>的调用位置等信息。但由于调用<code>g</code>之后，函数<code>f</code>就结束了，所以执行到最后一步，完全可以删除<code>f(x)</code>的调用帧，只保留<code>g(3)</code>的调用帧。
    </p>
    <p>这就叫做“尾调用优化”（Tail call optimization），即只保留内层函数的调用帧。如果所有函数都是尾调用，那么完全可以做到每次执行时，调用帧只有一项，这将大大节省内存。这就是“尾调用优化”的意义。</p>
    <p>注意，只有不再用到外层函数的内部变量，内层函数的调用帧才会取代外层函数的调用帧，否则就无法进行“尾调用优化”。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">addOne</span>(<span
            class="pl-smi">a</span>){
  <span class="pl-k">var</span> one <span class="pl-k">=</span> <span class="pl-c1">1</span>;
  <span class="pl-k">function</span> <span class="pl-en">inner</span>(<span class="pl-smi">b</span>){
    <span class="pl-k">return</span> b <span class="pl-k">+</span> one;
  }
  <span class="pl-k">return</span> <span class="pl-en">inner</span>(a);
}</pre>
    </div>
    <p>上面的函数不会进行尾调用优化，因为内层函数<code>inner</code>用到了外层函数<code>addOne</code>的内部变量<code>one</code>。</p>
    <h3><a href="#尾递归"  class="anchor" id="user-content-尾递归">

    </a>尾递归
    </h3>
    <p>函数调用自身，称为递归。如果尾调用自身，就称为尾递归。</p>
    <p>递归非常耗费内存，因为需要同时保存成千上百个调用帧，很容易发生“栈溢出”错误（stack overflow）。但对于尾递归来说，由于只存在一个调用帧，所以永远不会发生“栈溢出”错误。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">factorial</span>(<span class="pl-smi">n</span>) {
  <span class="pl-k">if</span> (n <span class="pl-k">===</span> <span class="pl-c1">1</span>) <span
                class="pl-k">return</span> <span class="pl-c1">1</span>;
  <span class="pl-k">return</span> n <span class="pl-k">*</span> <span class="pl-en">factorial</span>(n <span
                class="pl-k">-</span> <span class="pl-c1">1</span>);
}

<span class="pl-en">factorial</span>(<span class="pl-c1">5</span>) <span class="pl-c"><span
                class="pl-c">//</span> 120</span></pre>
    </div>
    <p>上面代码是一个阶乘函数，计算<code>n</code>的阶乘，最多需要保存<code>n</code>个调用记录，复杂度 O(n) 。</p>
    <p>如果改写成尾递归，只保留一个调用记录，复杂度 O(1) 。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">factorial</span>(<span class="pl-smi">n</span>, <span class="pl-smi">total</span>) {
  <span class="pl-k">if</span> (n <span class="pl-k">===</span> <span class="pl-c1">1</span>) <span
                class="pl-k">return</span> total;
  <span class="pl-k">return</span> <span class="pl-en">factorial</span>(n <span class="pl-k">-</span> <span
                class="pl-c1">1</span>, n <span class="pl-k">*</span> total);
}

<span class="pl-en">factorial</span>(<span class="pl-c1">5</span>, <span class="pl-c1">1</span>) <span
                class="pl-c"><span class="pl-c">//</span> 120</span></pre>
    </div>
    <p>还有一个比较著名的例子，就是计算 Fibonacci 数列，也能充分说明尾递归优化的重要性。</p>
    <p>非尾递归的 Fibonacci 数列实现如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">Fibonacci</span> (<span class="pl-smi">n</span>) {
  <span class="pl-k">if</span> ( n <span class="pl-k">&lt;=</span> <span class="pl-c1">1</span> ) {<span class="pl-k">return</span> <span
                class="pl-c1">1</span>};

  <span class="pl-k">return</span> <span class="pl-en">Fibonacci</span>(n <span class="pl-k">-</span> <span
                class="pl-c1">1</span>) <span class="pl-k">+</span> <span class="pl-en">Fibonacci</span>(n <span
                class="pl-k">-</span> <span class="pl-c1">2</span>);
}

<span class="pl-en">Fibonacci</span>(<span class="pl-c1">10</span>) <span class="pl-c"><span
                class="pl-c">//</span> 89</span>
<span class="pl-en">Fibonacci</span>(<span class="pl-c1">100</span>) <span class="pl-c"><span class="pl-c">//</span> 堆栈溢出</span>
<span class="pl-en">Fibonacci</span>(<span class="pl-c1">500</span>) <span class="pl-c"><span class="pl-c">//</span> 堆栈溢出</span></pre>
    </div>
    <p>尾递归优化过的 Fibonacci 数列实现如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">Fibonacci2</span> (<span class="pl-smi">n</span> , <span class="pl-smi">ac1</span> <span
            class="pl-k">=</span> <span class="pl-c1">1</span> , <span class="pl-smi">ac2</span> <span
            class="pl-k">=</span> <span class="pl-c1">1</span>) {
  <span class="pl-k">if</span>( n <span class="pl-k">&lt;=</span> <span class="pl-c1">1</span> ) {<span class="pl-k">return</span> ac2};

  <span class="pl-k">return</span> <span class="pl-en">Fibonacci2</span> (n <span class="pl-k">-</span> <span
                class="pl-c1">1</span>, ac2, ac1 <span class="pl-k">+</span> ac2);
}

<span class="pl-en">Fibonacci2</span>(<span class="pl-c1">100</span>) <span class="pl-c"><span class="pl-c">//</span> 573147844013817200000</span>
<span class="pl-en">Fibonacci2</span>(<span class="pl-c1">1000</span>) <span class="pl-c"><span class="pl-c">//</span> 7.0330367711422765e+208</span>
<span class="pl-en">Fibonacci2</span>(<span class="pl-c1">10000</span>) <span class="pl-c"><span class="pl-c">//</span> Infinity</span></pre>
    </div>
    <p>由此可见，“尾调用优化”对递归操作意义重大，所以一些函数式编程语言将其写入了语言规格。ES6 是如此，第一次明确规定，所有 ECMAScript 的实现，都必须部署“尾调用优化”。这就是说，ES6
        中只要使用尾递归，就不会发生栈溢出，相对节省内存。</p>
    <h3><a href="#递归函数的改写"  class="anchor" id="user-content-递归函数的改写">

    </a>递归函数的改写
    </h3>
    <p>尾递归的实现，往往需要改写递归函数，确保最后一步只调用自身。做到这一点的方法，就是把所有用到的内部变量改写成函数的参数。比如上面的例子，阶乘函数 factorial 需要用到一个中间变量<code>total</code>，那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观，第一眼很难看出来，为什么计算<code>5</code>的阶乘，需要传入两个参数<code>5</code>和<code>1</code>？
    </p>
    <p>两个方法可以解决这个问题。方法一是在尾递归函数之外，再提供一个正常形式的函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">tailFactorial</span>(<span class="pl-smi">n</span>, <span class="pl-smi">total</span>) {
  <span class="pl-k">if</span> (n <span class="pl-k">===</span> <span class="pl-c1">1</span>) <span
                class="pl-k">return</span> total;
  <span class="pl-k">return</span> <span class="pl-en">tailFactorial</span>(n <span class="pl-k">-</span> <span
                class="pl-c1">1</span>, n <span class="pl-k">*</span> total);
}

<span class="pl-k">function</span> <span class="pl-en">factorial</span>(<span class="pl-smi">n</span>) {
  <span class="pl-k">return</span> <span class="pl-en">tailFactorial</span>(n, <span class="pl-c1">1</span>);
}

<span class="pl-en">factorial</span>(<span class="pl-c1">5</span>) <span class="pl-c"><span
                class="pl-c">//</span> 120</span></pre>
    </div>
    <p>上面代码通过一个正常形式的阶乘函数<code>factorial</code>，调用尾递归函数<code>tailFactorial</code>，看起来就正常多了。</p>
    <p>函数式编程有一个概念，叫做柯里化（currying），意思是将多参数的函数转换成单参数的形式。这里也可以使用柯里化。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">currying</span>(<span class="pl-smi">fn</span>, <span class="pl-smi">n</span>) {
  <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">m</span>) {
    <span class="pl-k">return</span> <span class="pl-smi">fn</span>.<span class="pl-c1">call</span>(<span class="pl-c1">this</span>, m, n);
  };
}

<span class="pl-k">function</span> <span class="pl-en">tailFactorial</span>(<span class="pl-smi">n</span>, <span
                class="pl-smi">total</span>) {
  <span class="pl-k">if</span> (n <span class="pl-k">===</span> <span class="pl-c1">1</span>) <span
                class="pl-k">return</span> total;
  <span class="pl-k">return</span> <span class="pl-en">tailFactorial</span>(n <span class="pl-k">-</span> <span
                class="pl-c1">1</span>, n <span class="pl-k">*</span> total);
}

<span class="pl-k">const</span> <span class="pl-c1">factorial</span> <span class="pl-k">=</span> <span class="pl-en">currying</span>(tailFactorial, <span
                class="pl-c1">1</span>);

<span class="pl-en">factorial</span>(<span class="pl-c1">5</span>) <span class="pl-c"><span
                class="pl-c">//</span> 120</span></pre>
    </div>
    <p>上面代码通过柯里化，将尾递归函数<code>tailFactorial</code>变为只接受一个参数的<code>factorial</code>。</p>
    <p>第二种方法就简单多了，就是采用 ES6 的函数默认值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">factorial</span>(<span class="pl-smi">n</span>, <span class="pl-smi">total</span> <span
            class="pl-k">=</span> <span class="pl-c1">1</span>) {
  <span class="pl-k">if</span> (n <span class="pl-k">===</span> <span class="pl-c1">1</span>) <span
                class="pl-k">return</span> total;
  <span class="pl-k">return</span> <span class="pl-en">factorial</span>(n <span class="pl-k">-</span> <span
                class="pl-c1">1</span>, n <span class="pl-k">*</span> total);
}

<span class="pl-en">factorial</span>(<span class="pl-c1">5</span>) <span class="pl-c"><span
                class="pl-c">//</span> 120</span></pre>
    </div>
    <p>上面代码中，参数<code>total</code>有默认值<code>1</code>，所以调用时不用提供这个值。</p>
    <p>总结一下，递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令，所有的循环都用递归实现，这就是为什么尾递归对这些语言极其重要。对于其他支持“尾调用优化”的语言（比如
        Lua，ES6），只需要知道循环可以用递归代替，而一旦使用递归，就最好使用尾递归。</p>
    <h3><a href="#严格模式-1"  class="anchor" id="user-content-严格模式-1">

    </a>严格模式
    </h3>
    <p>ES6 的尾调用优化只在严格模式下开启，正常模式是无效的。</p>
    <p>这是因为在正常模式下，函数内部有两个变量，可以跟踪函数的调用栈。</p>
    <ul>
        <li><code>func.arguments</code>：返回调用时函数的参数。</li>
        <li><code>func.caller</code>：返回调用当前函数的那个函数。</li>
    </ul>
    <p>尾调用优化发生时，函数的调用栈会改写，因此上面两个变量就会失真。严格模式禁用这两个变量，所以尾调用模式仅在严格模式下生效。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">restricted</span>() {
  <span class="pl-s"><span class="pl-pds">'</span>use strict<span class="pl-pds">'</span></span>;
  <span class="pl-smi">restricted</span>.<span class="pl-c1">caller</span>;    <span class="pl-c"><span
                class="pl-c">//</span> 报错</span>
  <span class="pl-smi">restricted</span>.<span class="pl-c1">arguments</span>; <span class="pl-c"><span
                class="pl-c">//</span> 报错</span>
}
<span class="pl-en">restricted</span>();</pre>
    </div>
    <h3><a href="#尾递归优化的实现"  class="anchor" id="user-content-尾递归优化的实现">

    </a>尾递归优化的实现
    </h3>
    <p>尾递归优化只在严格模式下生效，那么正常模式下，或者那些不支持该功能的环境中，有没有办法也使用尾递归优化呢？回答是可以的，就是自己实现尾递归优化。</p>
    <p>它的原理非常简单。尾递归之所以需要优化，原因是调用栈太多，造成溢出，那么只要减少调用栈，就不会溢出。怎么做可以减少调用栈呢？就是采用“循环”换掉“递归”。</p>
    <p>下面是一个正常的递归函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">sum</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span>) {
  <span class="pl-k">if</span> (y <span class="pl-k">&gt;</span> <span class="pl-c1">0</span>) {
    <span class="pl-k">return</span> <span class="pl-en">sum</span>(x <span class="pl-k">+</span> <span
                class="pl-c1">1</span>, y <span class="pl-k">-</span> <span class="pl-c1">1</span>);
  } <span class="pl-k">else</span> {
    <span class="pl-k">return</span> x;
  }
}

<span class="pl-en">sum</span>(<span class="pl-c1">1</span>, <span class="pl-c1">100000</span>)
<span class="pl-c"><span class="pl-c">//</span> Uncaught RangeError: Maximum call stack size exceeded(…)</span></pre>
    </div>
    <p>上面代码中，<code>sum</code>是一个递归函数，参数<code>x</code>是需要累加的值，参数<code>y</code>控制递归次数。一旦指定<code>sum</code>递归 100000
        次，就会报错，提示超出调用栈的最大次数。</p>
    <p>蹦床函数（trampoline）可以将递归执行转为循环执行。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">trampoline</span>(<span class="pl-smi">f</span>) {
  <span class="pl-k">while</span> (f <span class="pl-k">&amp;&amp;</span> f <span class="pl-k">instanceof</span> <span
                class="pl-c1">Function</span>) {
    f <span class="pl-k">=</span> <span class="pl-en">f</span>();
  }
  <span class="pl-k">return</span> f;
}</pre>
    </div>
    <p>上面就是蹦床函数的一个实现，它接受一个函数<code>f</code>作为参数。只要<code>f</code>执行后返回一个函数，就继续执行。注意，这里是返回一个函数，然后执行该函数，而不是函数里面调用函数，这样就避免了递归执行，从而就消除了调用栈过大的问题。
    </p>
    <p>然后，要做的就是将原来的递归函数，改写为每一步返回另一个函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">sum</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span>) {
  <span class="pl-k">if</span> (y <span class="pl-k">&gt;</span> <span class="pl-c1">0</span>) {
    <span class="pl-k">return</span> <span class="pl-smi">sum</span>.<span class="pl-en">bind</span>(<span
                class="pl-c1">null</span>, x <span class="pl-k">+</span> <span class="pl-c1">1</span>, y <span
                class="pl-k">-</span> <span class="pl-c1">1</span>);
  } <span class="pl-k">else</span> {
    <span class="pl-k">return</span> x;
  }
}</pre>
    </div>
    <p>上面代码中，<code>sum</code>函数的每次执行，都会返回自身的另一个版本。</p>
    <p>现在，使用蹦床函数执行<code>sum</code>，就不会发生调用栈溢出。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-en">trampoline</span>(<span class="pl-en">sum</span>(<span
            class="pl-c1">1</span>, <span class="pl-c1">100000</span>))
<span class="pl-c"><span class="pl-c">//</span> 100001</span></pre>
    </div>
    <p>蹦床函数并不是真正的尾递归优化，下面的实现才是。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">tco</span>(<span class="pl-smi">f</span>) {
  <span class="pl-k">var</span> value;
  <span class="pl-k">var</span> active <span class="pl-k">=</span> <span class="pl-c1">false</span>;
  <span class="pl-k">var</span> accumulated <span class="pl-k">=</span> [];

  <span class="pl-k">return</span> <span class="pl-k">function</span> <span class="pl-en">accumulator</span>() {
    <span class="pl-smi">accumulated</span>.<span class="pl-c1">push</span>(<span class="pl-c1">arguments</span>);
    <span class="pl-k">if</span> (<span class="pl-k">!</span>active) {
      active <span class="pl-k">=</span> <span class="pl-c1">true</span>;
      <span class="pl-k">while</span> (<span class="pl-smi">accumulated</span>.<span class="pl-c1">length</span>) {
        value <span class="pl-k">=</span> <span class="pl-smi">f</span>.<span class="pl-c1">apply</span>(<span
                class="pl-c1">this</span>, <span class="pl-smi">accumulated</span>.<span class="pl-c1">shift</span>());
      }
      active <span class="pl-k">=</span> <span class="pl-c1">false</span>;
      <span class="pl-k">return</span> value;
    }
  };
}

<span class="pl-k">var</span> sum <span class="pl-k">=</span> <span class="pl-en">tco</span>(<span
                class="pl-k">function</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span>) {
  <span class="pl-k">if</span> (y <span class="pl-k">&gt;</span> <span class="pl-c1">0</span>) {
    <span class="pl-k">return</span> <span class="pl-en">sum</span>(x <span class="pl-k">+</span> <span
                class="pl-c1">1</span>, y <span class="pl-k">-</span> <span class="pl-c1">1</span>)
  }
  <span class="pl-k">else</span> {
    <span class="pl-k">return</span> x
  }
});

<span class="pl-en">sum</span>(<span class="pl-c1">1</span>, <span class="pl-c1">100000</span>)
<span class="pl-c"><span class="pl-c">//</span> 100001</span></pre>
    </div>
    <p>
        上面代码中，<code>tco</code>函数是尾递归优化的实现，它的奥妙就在于状态变量<code>active</code>。默认情况下，这个变量是不激活的。一旦进入尾递归优化的过程，这个变量就激活了。然后，每一轮递归<code>sum</code>返回的都是<code>undefined</code>，所以就避免了递归执行；而<code>accumulated</code>数组存放每一轮<code>sum</code>执行的参数，总是有值的，这就保证了<code>accumulator</code>函数内部的<code>while</code>循环总是会执行。这样就很巧妙地将“递归”改成了“循环”，而后一轮的参数会取代前一轮的参数，保证了调用栈只有一层。
    </p>
    <h2><a href="#函数参数的尾逗号"  class="anchor" id="user-content-函数参数的尾逗号">

    </a>函数参数的尾逗号
    </h2>
    <p>ES2017 <a href="https://github.com/jeffmo/es-trailing-function-commas">允许</a>函数的最后一个参数有尾逗号（trailing comma）。</p>
    <p>此前，函数定义和调用时，都不允许最后一个参数后面出现逗号。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">clownsEverywhere</span>(
  <span class="pl-smi">param1</span>,
  <span class="pl-smi">param2</span>
) { <span class="pl-c"><span class="pl-c">/*</span> ... <span class="pl-c">*/</span></span> }

<span class="pl-en">clownsEverywhere</span>(
  <span class="pl-s"><span class="pl-pds">'</span>foo<span class="pl-pds">'</span></span>,
  <span class="pl-s"><span class="pl-pds">'</span>bar<span class="pl-pds">'</span></span>
);</pre>
    </div>
    <p>上面代码中，如果在<code>param2</code>或<code>bar</code>后面加一个逗号，就会报错。</p>
    <p>如果像上面这样，将参数写成多行（即每个参数占据一行），以后修改代码的时候，想为函数<code>clownsEverywhere</code>添加第三个参数，或者调整参数的次序，就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说，就会显示添加逗号的那一行也发生了变动。这看上去有点冗余，因此新的语法允许定义和调用时，尾部直接有一个逗号。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">clownsEverywhere</span>(
  <span class="pl-smi">param1</span>,
  <span class="pl-smi">param2</span>,
) { <span class="pl-c"><span class="pl-c">/*</span> ... <span class="pl-c">*/</span></span> }

<span class="pl-en">clownsEverywhere</span>(
  <span class="pl-s"><span class="pl-pds">'</span>foo<span class="pl-pds">'</span></span>,
  <span class="pl-s"><span class="pl-pds">'</span>bar<span class="pl-pds">'</span></span>,
);</pre>
    </div>
    <p>这样的规定也使得，函数参数与数组和对象的尾逗号规则，保持一致了。</p>
    <h2><a href="#catch-语句的参数"  class="anchor" id="user-content-catch-语句的参数">

    </a>catch 语句的参数
    </h2>
    <p>目前，有一个<a
            href="https://github.com/tc39/proposal-optional-catch-binding">提案</a>，允许<code>try...catch</code>结构中的<code>catch</code>语句调用时不带有参数。这个提案跟参数有关，也放在这一章介绍。
    </p>
    <p>传统的写法是<code>catch</code>语句必须带有参数，用来接收<code>try</code>代码块抛出的错误。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">try</span> {
  <span class="pl-c"><span class="pl-c">//</span>  ···</span>
} <span class="pl-k">catch</span> (error) {
  <span class="pl-c"><span class="pl-c">//</span>  ···</span>
}</pre>
    </div>
    <p>新的写法允许省略<code>catch</code>后面的参数，而不报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">try</span> {
  <span class="pl-c"><span class="pl-c">//</span>  ···</span>
} <span class="pl-k">catch</span> {
  <span class="pl-c"><span class="pl-c">//</span>  ···</span>
}</pre>
    </div>
    <p>新写法只在不需要错误实例的情况下有用，因此不及传统写法的用途广。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> jsonData;
<span class="pl-k">try</span> {
  jsonData <span class="pl-k">=</span> <span class="pl-c1">JSON</span>.<span class="pl-c1">parse</span>(str);
} <span class="pl-k">catch</span> {
  jsonData <span class="pl-k">=</span> <span class="pl-c1">DEFAULT_DATA</span>;
}</pre>
    </div>
    <p>上面代码中，<code>JSON.parse</code>报错只有一种可能：解析失败。因此，可以不需要抛出的错误实例。</p>
</div>
</body>
</html>
